#include <deque>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <string>

#include "tpe.h++"
#include "tryte.h++"

int main(int, char**);
void load(const std::string&);
Tryte& getMemory(const Tryte_t&);
Trit_t getOverflowFlag();
Trit_t getSignFlag();
void setSignFlag(const Tryte&);
void dump();

Tryte registers[N_REGS];
std::deque<Tryte> memory;
size_t memoryOffset = 0;

int main(int argc, char** argv) {
	if (argc != 2) {
		std::cout << "Usage: " << argv[0] << " file\n";
		return 1;
	}
	load(argv[1]);
	std::string input;
	while (true) {
		if (input.size() == 0) {
			dump();
			getline(std::cin, input);
		}
		Tryte_t instr = registers[REG_INSTR].getInt();
		Tryte_t dst = getMemory(instr + 1).getInt();
		Tryte_t src = getMemory(instr + 2).getInt();
		switch (getMemory(instr).getInt()) {
			case OP_HALT:
				std::cout << "halt\n";
				dump();
				return 0;
			case OP_NOP:
				std::cout << "nop\n";
				registers[REG_INSTR].opInc();
				break;
			case OP_SET_I:
				std::cout << "set " << regNames[dst] << " " << src << "\n";
				registers[dst].opSet(src);
				registers[REG_INSTR].opInc3();
				break;
			case OP_SET_R:
				std::cout << "set " << regNames[dst] << " " << regNames[src] << "\n";
				registers[dst].opSet(registers[src]);
				registers[REG_INSTR].opInc3();
				break;
			case OP_READ_I:
				std::cout << "read " << regNames[dst] << " " << src << "\n";
				registers[dst].opSet(getMemory(src));
				registers[REG_INSTR].opInc3();
				break;
			case OP_READ_R:
				std::cout << "read " << regNames[dst] << " " << regNames[src] << "\n";
				registers[dst].opSet(getMemory(registers[src].getInt()));
				registers[REG_INSTR].opInc3();
				break;
			case OP_WRITE_I:
				std::cout << "write " << regNames[dst] << " " << src << "\n";
				getMemory(registers[dst].getInt()).opSet(src);
				registers[REG_INSTR].opInc3();
				break;
			case OP_WRITE_R:
				std::cout << "write " << regNames[dst] << " " << regNames[src] << "\n";
				getMemory(registers[dst].getInt()).opSet(registers[src]);
				registers[REG_INSTR].opInc3();
				break;
			case OP_NOT:
				std::cout << "not " << regNames[dst] << "\n";
				registers[dst].opNot();
				setSignFlag(registers[dst]);
				registers[REG_INSTR].opInc();
				break;
			case OP_AND_I:
				std::cout << "and " << regNames[dst] << " " << src << "\n";
				registers[dst].opAnd(src);
				setSignFlag(registers[dst]);
				registers[REG_INSTR].opInc3();
				break;
			case OP_AND_R:
				std::cout << "and " << regNames[dst] << " " << regNames[src] << "\n";
				registers[dst].opAnd(registers[src]);
				setSignFlag(registers[dst]);
				registers[REG_INSTR].opInc3();
				break;
			case OP_OR_I:
				std::cout << "or " << regNames[dst] << " " << src << "\n";
				registers[dst].opOr(src);
				setSignFlag(registers[dst]);
				registers[REG_INSTR].opInc3();
				break;
			case OP_OR_R:
				std::cout << "or " << regNames[dst] << " " << regNames[src] << "\n";
				registers[dst].opOr(registers[src]);
				setSignFlag(registers[dst]);
				registers[REG_INSTR].opInc3();
				break;
			case OP_XOR_I:
				std::cout << "xor " << regNames[dst] << " " << src << "\n";
				registers[dst].opXor(src);
				setSignFlag(registers[dst]);
				registers[REG_INSTR].opInc3();
				break;
			case OP_XOR_R:
				std::cout << "xor " << regNames[dst] << " " << regNames[src] << "\n";
				registers[dst].opXor(registers[src]);
				setSignFlag(registers[dst]);
				registers[REG_INSTR].opInc3();
				break;
			case OP_SHL_I:
				std::cout << "shl " << regNames[dst] << " " << src << "\n";
				registers[REG_FLAGS].setTrit(FLAG_OVERFLOW, registers[dst].opShl(src, 0));
				setSignFlag(registers[dst]);
				registers[REG_INSTR].opInc3();
				break;
			case OP_SHL_R:
				std::cout << "shl " << regNames[dst] << " " << regNames[src] << "\n";
				registers[REG_FLAGS].setTrit(FLAG_OVERFLOW, registers[dst].opShl(registers[src], 0));
				setSignFlag(registers[dst]);
				registers[REG_INSTR].opInc3();
				break;
			case OP_SHR_I:
				std::cout << "shr " << regNames[dst] << " " << src << "\n";
				registers[REG_FLAGS].setTrit(FLAG_OVERFLOW, registers[dst].opShr(src, 0));
				setSignFlag(registers[dst]);
				registers[REG_INSTR].opInc3();
				break;
			case OP_SHR_R:
				std::cout << "shr " << regNames[dst] << " " << regNames[src] << "\n";
				registers[REG_FLAGS].setTrit(FLAG_OVERFLOW, registers[dst].opShr(registers[src], 0));
				setSignFlag(registers[dst]);
				registers[REG_INSTR].opInc3();
				break;
			case OP_SHLC_I:
				std::cout << "shlc " << regNames[dst] << " " << src << "\n";
				registers[REG_FLAGS].setTrit(FLAG_OVERFLOW, registers[dst].opShl(src, getOverflowFlag()));
				setSignFlag(registers[dst]);
				registers[REG_INSTR].opInc3();
				break;
			case OP_SHLC_R:
				std::cout << "shlc " << regNames[dst] << " " << regNames[src] << "\n";
				registers[REG_FLAGS].setTrit(FLAG_OVERFLOW, registers[dst].opShl(registers[src], getOverflowFlag()));
				setSignFlag(registers[dst]);
				registers[REG_INSTR].opInc3();
				break;
			case OP_SHRC_I:
				std::cout << "shrc " << regNames[dst] << " " << src << "\n";
				registers[REG_FLAGS].setTrit(FLAG_OVERFLOW, registers[dst].opShr(src, getOverflowFlag()));
				setSignFlag(registers[dst]);
				registers[REG_INSTR].opInc3();
				break;
			case OP_SHRC_R:
				std::cout << "shrc " << regNames[dst] << " " << regNames[src] << "\n";
				registers[REG_FLAGS].setTrit(FLAG_OVERFLOW, registers[dst].opShr(registers[src], getOverflowFlag()));
				setSignFlag(registers[dst]);
				registers[REG_INSTR].opInc3();
				break;
			case OP_INC:
				std::cout << "inc " << regNames[dst] << "\n";
				registers[dst].opInc();
				setSignFlag(registers[dst]);
				registers[REG_INSTR].opInc3();
				registers[REG_INSTR].opDec();
				break;
			case OP_INC3:
				std::cout << "inc3 " << regNames[dst] << "\n";
				registers[dst].opInc3();
				setSignFlag(registers[dst]);
				registers[REG_INSTR].opInc3();
				registers[REG_INSTR].opDec();
				break;
			case OP_DEC:
				std::cout << "dec " << regNames[dst] << "\n";
				registers[dst].opDec();
				setSignFlag(registers[dst]);
				registers[REG_INSTR].opInc3();
				registers[REG_INSTR].opDec();
				break;
			case OP_DEC3:
				std::cout << "dec3 " << regNames[dst] << "\n";
				registers[dst].opDec3();
				setSignFlag(registers[dst]);
				registers[REG_INSTR].opInc3();
				registers[REG_INSTR].opDec();
				break;
			case OP_ADD_I:
				std::cout << "add " << regNames[dst] << " " << src << "\n";
				registers[REG_FLAGS].setTrit(FLAG_OVERFLOW, registers[dst].opAdd(src, 0));
				setSignFlag(registers[dst]);
				registers[REG_INSTR].opInc3();
				break;
			case OP_ADD_R:
				std::cout << "add " << regNames[dst] << " " << regNames[src] << "\n";
				registers[REG_FLAGS].setTrit(FLAG_OVERFLOW, registers[dst].opAdd(registers[src], 0));
				setSignFlag(registers[dst]);
				registers[REG_INSTR].opInc3();
				break;
			case OP_SUB_I:
				std::cout << "sub " << regNames[dst] << " " << src << "\n";
				registers[REG_FLAGS].setTrit(FLAG_OVERFLOW, registers[dst].opSub(src, 0));
				setSignFlag(registers[dst]);
				registers[REG_INSTR].opInc3();
				break;
			case OP_SUB_R:
				std::cout << "sub " << regNames[dst] << " " << regNames[src] << "\n";
				registers[REG_FLAGS].setTrit(FLAG_OVERFLOW, registers[dst].opSub(registers[src], 0));
				setSignFlag(registers[dst]);
				registers[REG_INSTR].opInc3();
				break;
			case OP_CMP_I:
				std::cout << "cmp " << regNames[dst] << " " << src << "\n";
				{
					Tryte comparison(registers[dst]);
					registers[REG_FLAGS].setTrit(FLAG_OVERFLOW, comparison.opSub(src, 0));
					setSignFlag(comparison);
				}
				registers[REG_INSTR].opInc3();
				break;
			case OP_CMP_R:
				std::cout << "cmp " << regNames[dst] << " " << regNames[src] << "\n";
				{
					Tryte comparison(registers[dst]);
					registers[REG_FLAGS].setTrit(FLAG_OVERFLOW, comparison.opSub(registers[src], 0));
					setSignFlag(comparison);
				}
				registers[REG_INSTR].opInc3();
				break;
			case OP_ADDC_I:
				std::cout << "addc " << regNames[dst] << " " << src << "\n";
				registers[REG_FLAGS].setTrit(FLAG_OVERFLOW, registers[dst].opAdd(src, getOverflowFlag()));
				setSignFlag(registers[dst]);
				registers[REG_INSTR].opInc3();
				break;
			case OP_ADDC_R:
				std::cout << "addc " << regNames[dst] << " " << regNames[src] << "\n";
				registers[REG_FLAGS].setTrit(FLAG_OVERFLOW, registers[dst].opAdd(registers[src], getOverflowFlag()));
				setSignFlag(registers[dst]);
				registers[REG_INSTR].opInc3();
				break;
			case OP_SUBC_I:
				std::cout << "subc " << regNames[dst] << " " << src << "\n";
				registers[REG_FLAGS].setTrit(FLAG_OVERFLOW, registers[dst].opSub(src, getOverflowFlag()));
				setSignFlag(registers[dst]);
				registers[REG_INSTR].opInc3();
				break;
			case OP_SUBC_R:
				std::cout << "subc " << regNames[dst] << " " << regNames[src] << "\n";
				registers[REG_FLAGS].setTrit(FLAG_OVERFLOW, registers[dst].opSub(registers[src], getOverflowFlag()));
				setSignFlag(registers[dst]);
				registers[REG_INSTR].opInc3();
				break;
			case OP_SON:
				std::cout << "son\n";
				registers[REG_FLAGS].setTrit(FLAG_OVERFLOW, -1);
				registers[REG_INSTR].opInc();
				break;
			case OP_SOZ:
				std::cout << "soz\n";
				registers[REG_FLAGS].setTrit(FLAG_OVERFLOW, 0);
				registers[REG_INSTR].opInc();
				break;
			case OP_SOP:
				std::cout << "sop\n";
				registers[REG_FLAGS].setTrit(FLAG_OVERFLOW, 1);
				registers[REG_INSTR].opInc();
				break;
			case OP_SSN:
				std::cout << "ssn\n";
				registers[REG_FLAGS].setTrit(FLAG_SIGN, -1);
				registers[REG_INSTR].opInc();
				break;
			case OP_SSZ:
				std::cout << "ssz\n";
				registers[REG_FLAGS].setTrit(FLAG_SIGN, 0);
				registers[REG_INSTR].opInc();
				break;
			case OP_SSP:
				std::cout << "ssp\n";
				registers[REG_FLAGS].setTrit(FLAG_SIGN, 1);
				registers[REG_INSTR].opInc();
				break;
			case OP_JMP:
				std::cout << "jmp " << dst << "\n";
				registers[REG_INSTR].opAdd(dst, 0);
				registers[REG_INSTR].opInc3();
				registers[REG_INSTR].opDec();
				break;
			case OP_JON:
				std::cout << "jon " << dst << "\n";
				if (getOverflowFlag() == -1) {
					registers[REG_INSTR].opAdd(dst, 0);
				}
				registers[REG_INSTR].opInc3();
				registers[REG_INSTR].opDec();
				break;
			case OP_JOZ:
				std::cout << "joz " << dst << "\n";
				if (getOverflowFlag() == 0) {
					registers[REG_INSTR].opAdd(dst, 0);
				}
				registers[REG_INSTR].opInc3();
				registers[REG_INSTR].opDec();
				break;
			case OP_JOP:
				std::cout << "jop " << dst << "\n";
				if (getOverflowFlag() == 1) {
					registers[REG_INSTR].opAdd(dst, 0);
				}
				registers[REG_INSTR].opInc3();
				registers[REG_INSTR].opDec();
				break;
			case OP_JONN:
				std::cout << "jonn " << dst << "\n";
				if (getOverflowFlag() != -1) {
					registers[REG_INSTR].opAdd(dst, 0);
				}
				registers[REG_INSTR].opInc3();
				registers[REG_INSTR].opDec();
				break;
			case OP_JONZ:
				std::cout << "jonz " << dst << "\n";
				if (getOverflowFlag() != 0) {
					registers[REG_INSTR].opAdd(dst, 0);
				}
				registers[REG_INSTR].opInc3();
				registers[REG_INSTR].opDec();
				break;
			case OP_JONP:
				std::cout << "jonp " << dst << "\n";
				if (getOverflowFlag() != 1) {
					registers[REG_INSTR].opAdd(dst, 0);
				}
				registers[REG_INSTR].opInc3();
				registers[REG_INSTR].opDec();
				break;
			case OP_JSN:
				std::cout << "jsn " << dst << "\n";
				if (getSignFlag() == -1) {
					registers[REG_INSTR].opAdd(dst, 0);
				}
				registers[REG_INSTR].opInc3();
				registers[REG_INSTR].opDec();
				break;
			case OP_JSZ:
				std::cout << "jsz " << dst << "\n";
				if (getSignFlag() == 0) {
					registers[REG_INSTR].opAdd(dst, 0);
				}
				registers[REG_INSTR].opInc3();
				registers[REG_INSTR].opDec();
				break;
			case OP_JSP:
				std::cout << "jsp " << dst << "\n";
				if (getSignFlag() == 1) {
					registers[REG_INSTR].opAdd(dst, 0);
				}
				registers[REG_INSTR].opInc3();
				registers[REG_INSTR].opDec();
				break;
			case OP_JSNN:
				std::cout << "jsnn " << dst << "\n";
				if (getSignFlag() != -1) {
					registers[REG_INSTR].opAdd(dst, 0);
				}
				registers[REG_INSTR].opInc3();
				registers[REG_INSTR].opDec();
				break;
			case OP_JSNZ:
				std::cout << "jsnz " << dst << "\n";
				if (getSignFlag() != 0) {
					registers[REG_INSTR].opAdd(dst, 0);
				}
				registers[REG_INSTR].opInc3();
				registers[REG_INSTR].opDec();
				break;
			case OP_JSNP:
				std::cout << "jsnp " << dst << "\n";
				if (getSignFlag() != 1) {
					registers[REG_INSTR].opAdd(dst, 0);
				}
				registers[REG_INSTR].opInc3();
				registers[REG_INSTR].opDec();
				break;
			default:
				std::cout << "ERROR: unrecognised instruction: " << getMemory(instr).getInt() << " at " << instr << "\n";
				return 1;
		}
	}
}

void load(const std::string& filename) {
	std::ifstream input(filename);
	if (input.is_open() == false) {
		std::cout << "ERROR: unable to open file: '" << filename << "'\n";
		exit(1);
	}
	std::string value;
	while (input.eof() == false) {
		getline(input, value);
		if (value.find('#') != std::string::npos) {
			value.erase(value.find('#'));
		}
		if (value.find_first_not_of("\t ") != std::string::npos) {
			value.erase(0, value.find_first_not_of("\t "));
		}
		if (value.find_last_not_of("\t ") != std::string::npos) {
			value.erase(value.find_last_not_of("\t ") + 1);
		}
		if (value.size() == 0) {
			continue;
		}
		if (value.size() != TRITS_PER_TRYTE || value.find_first_not_of("+0-") != std::string::npos) {
			std::cout << "ERROR: invalid input: '" << value << "'\n";
			exit(1);
		}
		memory.push_back(Tryte(value));
	}
	input.close();
	registers[REG_A].opSet(0);
	registers[REG_B].opSet(0);
	registers[REG_C].opSet(0);
	registers[REG_D].opSet(0);
	registers[REG_E].opSet(0);
	registers[REG_FLAGS].opSet(0);
	registers[REG_FRAME].opSet(0);
	registers[REG_INSTR].opSet(0);
	registers[REG_STACK].opSet(memory.size());
}

Tryte& getMemory(const Tryte_t& address) {
	if (address + memoryOffset < 0) {
		memory.insert(memory.begin(), -(address + memoryOffset), Tryte(0));
		memoryOffset = -address;
	} else if (address + memoryOffset >= memory.size()) {
		memory.insert(memory.end(), address + memoryOffset - memory.size() + 1, Tryte(0));
	}
	return memory.at(address + memoryOffset);
}

Trit_t getOverflowFlag() {
	return registers[REG_FLAGS].getTrit(FLAG_OVERFLOW);
}

Trit_t getSignFlag() {
	return registers[REG_FLAGS].getTrit(FLAG_SIGN);
}

void setSignFlag(const Tryte& tryte) {
	if (tryte.getInt() > 0) {
		registers[REG_FLAGS].setTrit(FLAG_SIGN, 1);
	} else if (tryte.getInt() < 0) {
		registers[REG_FLAGS].setTrit(FLAG_SIGN, -1);
	} else {
		registers[REG_FLAGS].setTrit(FLAG_SIGN, 0);
	}
}

void dump() {
	std::cout << "Memory:\n";
	for (std::deque<Tryte>::iterator i = memory.begin(); i != memory.end(); ++i) {
		std::cout << "[" << std::setw(5) << ((i - memory.begin()) - memoryOffset) << "] " << i->getString() << " : " << i->getInt() << "\n";
	}
	std::cout << "Registers:\n";
	for (int i = 0; i < N_REGS; ++i) {
		std::cout << "[" << std::setw(5) << regNames[i] << "] " << registers[i].getString() << " : " << registers[i].getInt() << "\n";
	}
}

